/**
 * 
 */
package com.dalonedrau.jogl.engine;

import com.dalonedrau.d20.Diceroller;
import com.dalonedrau.jogl.opengl.sprite.BasicParticleObject;
import com.dalonedrau.jogl.opengl.sprite.ParticleEmitter;
import com.dalonedrau.jogl.opengl.sprite.SpriteBase;
import com.dalonedrau.jogl.opengl.sprite.SpriteImageObject;
import com.dalonedrau.jogl.opengl.sprite.SpriteImageObjectFactory;
import com.dalonedrau.jogl.opengl.ui.GuiComponent;
import com.dalonedrau.jogl.opengl.ui.GuiComponentLibrary;
import com.dalonedrau.jogl.util.UtilityMethods;
import com.dalonedrau.vectors.Vector3;

/**
 * @author Donald
 */
public class Particle {
	/** the time in milliseconds that a map fog emitter should "live". */
	public static final long	EMITTER_MAP_FOG_LIFESPAN			= 2000;
	/**
	 * the time in milliseconds that a single map fog emitter particle should
	 * "live".
	 */
	public static final long	EMITTER_MAP_FOG_PARTICLE_LIFESPAN	= 400;
	/** the one and only instance of the <code>Particle</code> class. */
	private static Particle		instance;
	public static final int		PARTICLE_FIRE_UP					= 0;
	public static final int		PARTICLE_MAP_FOG					= 1;
	/**
	 * Gives access to the singleton instance of {@link Particle}.
	 * @return {@link Particle}
	 */
	public static Particle getInstance() {
		if (Particle.instance == null) {
			Particle.instance = new Particle();
		}
		return Particle.instance;
	}
	private ParticleEmitter[]	emitters;
	/**
	 * Adds an emitter.
	 * @param parent the emitter's parent component
	 * @param behavior the emitter's behavior
	 * @param position the emitter's position
	 */
	public void addEmitter(final GuiComponent parent, final int behavior,
			final Vector3 position) {
		try {
			// find a space for the new emitter
			if (emitters == null) {
				emitters = new ParticleEmitter[0];
			}
			int index = -1;
			for (int i = 0; i < emitters.length; i++) {
				if (emitters[i] == null) {
					index = i;
					break;
				}
				if (!emitters[i].exists()) {
					index = i;
					break;
				}
			}
			if (index == -1) {
				ParticleEmitter[] dest = 
					new ParticleEmitter[emitters.length + 1];
				System.arraycopy(emitters, 0, dest, 0, emitters.length);
				index = emitters.length;
				emitters = dest;
				dest = null;
			}
			if (emitters[index] == null) {
				emitters[index] = new ParticleEmitter();
			}
			// configure the new emitter
			configureEmitter(index, behavior, position);
			emitters[index].setParentId(parent.getId());
			emitters[index].setTimeStart(Time.getInstance().getFrameStart());
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Adds a particle to a "fire" emitter.
	 * @param emitter the emitter
	 * @throws Exception if an error occurs
	 */
	private void addFireParticle(final ParticleEmitter emitter) 
	throws Exception {
		// the change in x and y
		double dx, dy;
		// create a new particle positioned at emitter's position
		SpriteBase particle = new BasicParticleObject();
		particle.setParentRefId(emitter.getParentId());
		SpriteImageObject img =
				SpriteImageObjectFactory.getInstance().getImageById(
						emitter.getImageRef());
		particle.setImageRefId(emitter.getImageRef());
		particle.setWidth(img.getWidth());
		particle.setHeight(img.getHeight());
		img = null;
		particle.setPosition(new Vector3(emitter.getPosition()));
		// set random color based on ranges
		particle.setColor(getRandomColor(emitter));
		// add some randomness to the spread
		// convert the angle to radians
		double dir = emitter.getDirection();
		double rad = dir * ParticleEmitter.PI_DIV_180;
		double variation =
				Diceroller.getInstance().rolldXPlusY(emitter.getSpread(), -1);
		variation -= (double) emitter.getSpread() / 2f;
		variation /= 100f;
		// direction is based on the angles of a circle
		// 0 degrees will go to the right, 90 degrees to top, etc.
		// at 0 degrees, the emitter should send particles to the right,
		// so the change in the x-coordinate should be 1, while the
		// change in the y-coordinate should be 0
		dx = Math.cos(rad) + variation;
		dy = Math.sin(rad) + variation;
		particle.setVelocity(
				dx * emitter.getVelocity(), dy * emitter.getVelocity());
		// set the scale
		if (emitter.getScale() != 1f) {
			Sprite.getInstance().scaleSprite(
					particle, emitter.getScale(), emitter.getScale());
		}
		emitter.addParticle(particle);
		particle = null;
	}
	/**
	 * Adds a particle to a "map fog" emitter.
	 * @param emitter the emitter
	 * @throws Exception if an error occurs
	 */
	private void addFogParticle(final ParticleEmitter emitter)
			throws Exception {
		// create a new particle positioned at emitter's position
		SpriteBase particle = new BasicParticleObject();
		particle.setParentRefId(emitter.getParentId());
		SpriteImageObject img =
				SpriteImageObjectFactory.getInstance().getImageById(
						emitter.getImageRef());
		particle.setImageRefId(emitter.getImageRef());
		particle.setWidth(img.getWidth());
		particle.setHeight(img.getHeight());
		img = null;
		particle.setPosition(new Vector3(emitter.getPosition()));
		// assign a random color (greyish);
		float rand = Diceroller.getInstance().rollPercent();
		float[] randa = getRandomColor(emitter);
		randa[0] = rand;
		randa[1] = rand;
		randa[2] = rand;
		particle.setColor(randa);
		// assign a random position
		particle.getPosition().set(getRandomFogParticlePosition(emitter));
		// set the scale
		if (emitter.getScale() != 1f) {
			Sprite.getInstance().scaleSprite(
					particle, emitter.getScale(), emitter.getScale());
		}
		emitter.addParticle(particle);
		particle = null;
	}
	private void configureEmitter(final int index, final int behavior,
			final Vector3 position) throws Exception {
		emitters[index].setBehavior(behavior);
		emitters[index].setPosition(position);
		emitters[index].setExists(true);
		switch (emitters[index].getBehavior()) {
		case PARTICLE_FIRE_UP:
			emitters[index].setImage(
					SpriteImageObjectFactory.getInstance().getImageByName(
							"particle_small").getRefId());
			emitters[index].setDirection(0);
			emitters[index].setMax(100);
			emitters[index].setAlphaRange(100, 255);
			emitters[index].setSpread(30);
			emitters[index].setVelocity(2f);
			emitters[index].setLength(250);
			emitters[index].setScale(1f);
			break;
		case PARTICLE_MAP_FOG:
			emitters[index].setImage(
					SpriteImageObjectFactory.getInstance().getImageByName(
							"particle_fog").getRefId());
			emitters[index].setDirection(0);
			emitters[index].setMax(10);
			emitters[index].setAlphaRange(100, 255);
			emitters[index].setSpread(0);
			emitters[index].setVelocity(0f);
			emitters[index].setLength(0);
			emitters[index].setScale(0.75f);
			break;
		}
	}
	/**
	 * Gets a random color array.
	 * @return <code>float</code>
	 */
	private float[] getRandomColor(final ParticleEmitter emitter) {
		float[] color = new float[GlobalConsts.INT_004];
		color[0] = Diceroller.getInstance().rollPercent();
		if (color[0] < emitter.getMinR()) {
			color[0] = emitter.getMinR();
		} else if (color[0] > emitter.getMaxR()) {
			color[0] = emitter.getMaxR();
		}
		color[1] = Diceroller.getInstance().rollPercent();
		if (color[1] < emitter.getMinG()) {
			color[1] = emitter.getMinG();
		} else if (color[1] > emitter.getMaxG()) {
			color[1] = emitter.getMaxG();
		}
		color[2] = Diceroller.getInstance().rollPercent();
		if (color[2] < emitter.getMinB()) {
			color[2] = emitter.getMinB();
		} else if (color[2] > emitter.getMaxB()) {
			color[2] = emitter.getMaxB();
		}
		color[GlobalConsts.INT_003] = Diceroller.getInstance().rollPercent();
		if (color[GlobalConsts.INT_003] < emitter.getMinA()) {
			color[GlobalConsts.INT_003] = emitter.getMinA();
		} else if (color[GlobalConsts.INT_003] > emitter.getMaxA()) {
			color[GlobalConsts.INT_003] = emitter.getMaxA();
		}
		return color;
	}
	public void update() {
		try {
			if (emitters == null) {
				emitters = new ParticleEmitter[0];
			}
			// update each emitter
			for (int i = 0; i < emitters.length; i++) {
				if (emitters[i] == null) {
					continue;
				}
				if (!emitters[i].exists()) {
					continue;
				}
				GuiComponent c =
						GuiComponentLibrary.getInstance().getComponent(
								emitters[i].getParentId());
				if (!c.isVisible()) {
					emitters[i].setExists(false);
					continue;
				}
				// update the emitters when the game is not paused
				if (!Interface.getInstance().hasViewFlag(
						GlobalConsts.VW_00_PAUSED)) {
					// update the emitter based on its behavior
					switch (emitters[i].getBehavior()) {
					case PARTICLE_FIRE_UP:
						updateFireEmitter(emitters[i]);
						break;
					case PARTICLE_MAP_FOG:
						updateFogEmitter(emitters[i]);
						break;
					}
				}
				if (emitters[i].exists()) {
					emitters[i].render();
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	private void updateFireEmitter(final ParticleEmitter emitter)
			throws Exception {
		final long now = Time.getInstance().getFrameStart();
		long diff = now - emitter.getFrameStart();
		long maxdiff =
				UtilityMethods.getInstance().convertMilliToNano(1);
		// do we need to add a new particle?
		if (diff >= maxdiff) {
			addFireParticle(emitter);
		}
		for (int i = 0; i < emitter.getNumParticles(); i++) {
			// update particle's position
			Sprite.getInstance().moveSprite(emitter.getParticle(i));
			// is particle beyond the emitter's range?
			if (emitter.getParticle(i).getPosition().distance(
					emitter.getPosition()) > emitter.getLength()) {
				// reset particle to the origin
				emitter.getParticle(i).getPosition().set(emitter.getPosition());
			}
		}
		emitter.setFrameStart(now);
	}
	private Vector3 getRandomFogParticlePosition(final ParticleEmitter emitter) {
		Vector3 v = new Vector3(emitter.getPosition());
		int randx = Diceroller.getInstance().rolldXPlusY(
				17, -1);
		randx -= 24;
		int randy = Diceroller.getInstance().rolldXPlusY(
				17, -1);
		randy -= 24;
		v.move(randx, randy, 0);
		return v;
	}
	/**
	 * Updates the "map fog" particle emitter.
	 * @param emitter the emitter
	 * @throws Exception if an error occurs
	 */
	private void updateFogEmitter(final ParticleEmitter emitter)
			throws Exception {
		final long now = Time.getInstance().getFrameStart();
		final long emitterStart = emitter.getTimeStart();
		final long emitterLife =
				UtilityMethods.getInstance().convertNanoToMilli(
						now - emitterStart);
		// has the emitter reached the end of its life?
		if (emitterLife > Particle.EMITTER_MAP_FOG_LIFESPAN) {
			emitter.stop();
		} else {
			if (emitter.getNumParticles() < emitter.getMax()) {
				// is it time to add new particles?
				// a new particle is added every time we
				// pass 1/2 a particle's lifespan
				long numNeeded =
						(long) (emitterLife
							/ (Particle.EMITTER_MAP_FOG_PARTICLE_LIFESPAN
									* GlobalConsts.DIV_4));
				if (numNeeded > emitter.getNumParticles()) {
					addFogParticle(emitter);
				}
			}
			// update existing particles
			for (int i = 0; i < emitter.getNumParticles(); i++) {
				final long particleStart = emitter.getParticleStart(i);
				final long particleLife =
						UtilityMethods.getInstance().convertNanoToMilli(
								now - particleStart);
				final long lastParticleAllowed = emitterStart
						+ UtilityMethods
								.getInstance()
								.convertMilliToNano(
										Particle.EMITTER_MAP_FOG_LIFESPAN
												- Particle.EMITTER_MAP_FOG_PARTICLE_LIFESPAN);
				if (particleLife > Particle.EMITTER_MAP_FOG_PARTICLE_LIFESPAN) {
					// the particle has reached the end of its lifespan
					// is there enough time to regenerate it?
					if (now <= lastParticleAllowed) {
						// particle should reappear somewhere else
						// set random color
						float rand = Diceroller.getInstance()
								.rollPercent();
						float[] randa = getRandomColor(emitter);
						randa[0] = rand;
						randa[1] = rand;
						randa[2] = rand;
						emitter.getParticle(i).setColor(randa);
						// set random position
						emitter.getParticle(i).getPosition().set(
								getRandomFogParticlePosition(emitter));
						// reset start time
						emitter.setParticleStart(i, now);
					} else {
						// particle needs to fade completely
						emitter.getParticle(i).setAlpha(0f);
					}
				} else {
					// particle should fade more - update particle's
					// alpha
					float alpha = (float) (now - particleStart)
							/ (float) UtilityMethods
									.getInstance()
									.convertMilliToNano(
											Particle.EMITTER_MAP_FOG_PARTICLE_LIFESPAN);
					alpha = 1f - alpha;
					emitter.getParticle(i).setAlpha(alpha);
				}
			}
		}
	}
}
